package com.khalil.kkadmin.module.job.service;

import com.khalil.kkadmin.infrastructure.util.ApplicationContextUtil;
import com.khalil.kkadmin.common.util.JsonUtil;
import com.khalil.kkadmin.module.job.repository.KkJobTaskRepository;
import com.khalil.kkadmin.module.job.entity.KkJobTask;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Predicate;
import jakarta.persistence.criteria.Root;
import jakarta.transaction.Transactional;
import org.quartz.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;

@Service
public class KkJobTaskServiceQuartzImpl implements KkJobTaskService {

    @Autowired
    private Scheduler scheduler;

    @Autowired
    private KkJobTaskRepository repository;

    private static final String JOB_GROUP = "KK_DEFAULT";

    @Override
    public KkJobTask getJobTaskById(Long id) {
        return repository.findById(id).orElse(null);
    }

    @Override
    public List<KkJobTask> listJobTask(String keyword, String disabled, String jobType, String expressionType) {

        Specification<KkJobTask> specification = new Specification<>() {
            @Override
            public Predicate toPredicate(Root<KkJobTask> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
                List<Predicate> predicates = new ArrayList<>();
                if (StringUtils.hasText(keyword)) {
                    Predicate predicate = criteriaBuilder.like(root.get("name"), "%" + keyword + "%");
                    predicates.add(predicate);
                }
                if (StringUtils.hasText(disabled)) {
                    Predicate predicate = criteriaBuilder.equal(root.get("disabled"), disabled);
                    predicates.add(predicate);
                }
                if (StringUtils.hasText(jobType)) {
                    Predicate predicate = criteriaBuilder.equal(root.get("jobType"), jobType);
                    predicates.add(predicate);
                }
                if (StringUtils.hasText(expressionType)) {
                    Predicate predicate = criteriaBuilder.equal(root.get("expressionType"), expressionType);
                    predicates.add(predicate);
                }

                return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
            }
        };

        return repository.findAll(specification);
    }

    @Transactional
    @Override
    public void addJobTask(KkJobTask kkJobTask) {
        kkJobTask.setId(null);
        repository.save(kkJobTask);

        if ("0".equals(kkJobTask.getDisabled())) {
            // 添加到quartz
            schedulerQuartzJobAdd(kkJobTask);
        }
    }

    @Transactional
    @Override
    public void updateJobTask(KkJobTask kkJobTask) {
        // 更新数据库
        Long id = kkJobTask.getId();
        Optional<KkJobTask> optionalKkJobTask = repository.findById(id);

        if (optionalKkJobTask.isEmpty()) {
            throw new RuntimeException("任务不存在");
        }

        repository.save(kkJobTask);

        if ("0".equals(kkJobTask.getDisabled())) {
            schedulerQuartzJobDelete(kkJobTask);
            schedulerQuartzJobAdd(kkJobTask);
        } else {
            deleteJob(getJobName(id), JOB_GROUP);
        }
    }


    @Transactional
    @Override
    public void pauseJob(Long id) {
        Optional<KkJobTask> optionalKkJobTask = repository.findById(id);
        if (optionalKkJobTask.isPresent()) {
            KkJobTask kkJobTask = optionalKkJobTask.get();
            kkJobTask.setDisabled("1");
            repository.save(kkJobTask);

            schedulerQuartzJobDelete(kkJobTask);
        }
    }

    @Transactional
    @Override
    public void resumeJob(Long id) {
        Optional<KkJobTask> optionalKkJobTask = repository.findById(id);
        if (optionalKkJobTask.isPresent()) {
            KkJobTask kkJobTask = optionalKkJobTask.get();
            kkJobTask.setDisabled("0");
            repository.save(kkJobTask);

            schedulerQuartzJobAdd(kkJobTask);
        }
    }

    @Transactional
    @Override
    public void deleteJob(Long id) {
        Optional<KkJobTask> optionalKkJobTask = repository.findById(id);
        if (optionalKkJobTask.isPresent()) {
            KkJobTask kkJobTask = optionalKkJobTask.get();
            repository.deleteById(id);

            schedulerQuartzJobDelete(kkJobTask);
        }
    }

    public void schedulerQuartzJobAdd(KkJobTask kkJobTask) {
        String jobType = kkJobTask.getJobType();
        String className = kkJobTask.getClassName();
        try {
            Class<? extends Job> jobClass = null;
            if ("className".equals(jobType)) {
                Class<?> aClass = Class.forName(className);
                if (!Job.class.isAssignableFrom(aClass)) {
                    throw new RuntimeException("className must be a subclass of Job");
                }
                jobClass = (Class<? extends Job>) aClass;
            } else if ("beanName".equals(jobType)) {
                Job bean = ApplicationContextUtil.getBean(className, Job.class);
                jobClass = bean.getClass();
            } else {
                throw new RuntimeException("jobType must be className or beanName");
            }

            String expressionType = kkJobTask.getExpressionType();
            String paramDataMap = kkJobTask.getParamDataMap();
            Map jobDataMap = null;
            if (StringUtils.hasText(paramDataMap)) {
                jobDataMap = JsonUtil.parseJson2Object(paramDataMap, Map.class);
                System.out.println("jobDataMap = " + jobDataMap);
            }

            if ("simple".equals(expressionType)) {
                if (kkJobTask.getExpression().matches("^\\d+$")) {
                    addSimpleJob(getJobName(kkJobTask.getId()), JOB_GROUP, jobClass, Integer.parseInt(kkJobTask.getExpression()), jobDataMap);
                } else {
                    throw new RuntimeException("expression must be a number");
                }
            } else if ("cron".equals(expressionType)) {
                addCronJob(getJobName(kkJobTask.getId()), JOB_GROUP, jobClass, kkJobTask.getExpression(), jobDataMap);
            }
            // 启动scheduler
            scheduler.start();
        } catch (ClassNotFoundException e) {
            throw new RuntimeException("系统中不存在类：" + className);
        } catch (SchedulerException e) {
            throw new RuntimeException(e);
        }
    }


    public void schedulerQuartzJobDelete(KkJobTask kkJobTask) {
        deleteJob(getJobName(kkJobTask.getId()), JOB_GROUP);
    }

    /**
     * 添加任务
     *
     * @param jobName  任务名称
     * @param jobGroup 任务组
     * @param jobClass 任务类
     * @param cron     Cron表达式
     * @throws SchedulerException 抛出异常
     */
    private void addCronJob(String jobName, String jobGroup, Class<? extends Job> jobClass, String cron, Map<String, Object> jobDataMap) throws SchedulerException {
        JobDetail jobDetail = JobBuilder.newJob(jobClass)
                .withIdentity(jobName, jobGroup)
                .build();

        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity(jobName, jobGroup)
                .withSchedule(CronScheduleBuilder.cronSchedule(cron))
                .startNow()
                .build();

        if (!CollectionUtils.isEmpty(jobDataMap)) {
            trigger.getJobDataMap().putAll(jobDataMap);
        }

        scheduler.scheduleJob(jobDetail, trigger);
    }

    private void addSimpleJob(String jobName, String jobGroup, Class<? extends Job> jobClass, Integer seconds, Map<String, Object> jobDataMap) {
        JobDetail jobDetail = JobBuilder.newJob(jobClass)
                .withIdentity(jobName, jobGroup).build();

        Trigger trigger = TriggerBuilder.newTrigger().withSchedule(SimpleScheduleBuilder.simpleSchedule()
                        .repeatForever()
                        .withIntervalInSeconds(seconds))
                .startNow()
                .build();

        if (!CollectionUtils.isEmpty(jobDataMap)) {
            trigger.getJobDataMap().putAll(jobDataMap);
        }

        try {
            scheduler.scheduleJob(jobDetail, trigger);
        } catch (SchedulerException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 删除任务
     *
     * @param jobName  任务名称
     * @param jobGroup 任务组
     */
    public void deleteJob(String jobName, String jobGroup) {
        try {
            scheduler.pauseJob(JobKey.jobKey(jobName, jobGroup));
            scheduler.unscheduleJob(TriggerKey.triggerKey(jobName, jobGroup));
            scheduler.deleteJob(JobKey.jobKey(jobName, jobGroup));
        } catch (SchedulerException e) {
            throw new RuntimeException(e);
        }
    }


    private String getJobName(Long id) {
        return "" + id;
    }
}
